package model.generators.networkGenerators;

import model.File;
import model.Network;
import model.connectible.ISP;
import model.connectible.Peer;
import model.connections.Connection;
import model.networkTopology.Node;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class BarabasiAlbertGenerator implements NetworkGenerator {
	public final static BarabasiAlbertGenerator generator = new BarabasiAlbertGenerator();
	final static Random random = new Random();
	final static int nodesCount = 50;
	
	private final static int stage1Bandwidth = 5;
	private final static int stage2Bandwidth = 10;
	private final static int stage3Bandwidth = 20;

	private final static double stage1Stability = 0.9;
	private final static double stage2Stability = 0.95;
	private final static double stage3Stability = 0.99;

    private final static int MAX_FILE_SIZE = 10000;
    private final static int MAX_FILE_POOL_SIZE = 30;

    private final static int MIN_PEER_TIME_ON = 10;
    private final static int MIN_PEER_TIME_OFF = 10;
    private final static int MAX_PEER_TIME_ON = 90;
    private final static int MAX_PEER_TIME_OFF = 50;


    private int idCounter = 0;
    private List<File> wantedFilePool = new ArrayList<>();
    private List<File> seededFilePool = new ArrayList<>();
		
	
	public BarabasiAlbertGenerator() {
		
	}
	
	public Network getNetwork() {
		return generateConnections(convert(getTopology()));
	}
	
	public List<Node> getTopology() {
		List<Node> nodeList = new ArrayList<Node>();
		for (int i = 0; i < nodesCount; i++) {
			Node node = new Node(i);
			Node paired = selectRandomAttachment(nodeList);
			if (paired != null) {
				node.addLink(paired);
				paired.addLink(node);
			}
			nodeList.add(node);
		}
		return nodeList;
	}
	
	public Node selectRandomAttachment(List<Node> nodes) {
		if(nodes.size() == 0) {
			return null;
		} else if(nodes.size() == 1) {
			return nodes.get(0);
		} else {
			int rand = random.nextInt(2 * (nodes.size() -1) - 1);
			for (Node node : nodes) {
				rand -= node.getLinksSize();
				if (rand < 0) {
					return node;
				}
			}
		}
		return null;
	}
	
	public List<Node> convert(List<Node> nodes) {
		for (Node node : nodes) {
			if (node.getLinksSize() == 1) {
				node.setTrueForm(getRandomPeer());
			} else {
				node.setTrueForm(getRandomISP());
			}
		}
		return nodes;
	}

    private Peer getRandomPeer() {

        Peer peer = new Peer( getNextId(), MIN_PEER_TIME_ON + random.nextInt(MAX_PEER_TIME_ON), MIN_PEER_TIME_OFF + random.nextInt(MAX_PEER_TIME_OFF) );
//        peer.setOn(random.nextBoolean());
        peer.setOn(true);
        peer.setWantedFile(getRandomFile());
        int maxFiles = random.nextInt(MAX_FILE_POOL_SIZE / 2);
        for (int i = 0 ; i < maxFiles; i++) {
            peer.addFileToStorage(seededFilePool.get( random.nextInt(MAX_FILE_POOL_SIZE) ));
        }
        return peer;
    }

    private ISP getRandomISP() {
        ISP isp = new ISP();
        isp.setId(getNextId());
        return isp;
    }

    private File getRandomFile() {
        if (wantedFilePool.isEmpty()){
            createFilePool();
        }
        return wantedFilePool.get(random.nextInt(wantedFilePool.size()));
    }

    private void createFilePool() {

        for (int i = 0 ; i < MAX_FILE_POOL_SIZE; i++) {
            wantedFilePool.add(new File(getNextId(), random.nextInt(MAX_FILE_SIZE), false));
            seededFilePool.add(wantedFilePool.get(i).clone());
            seededFilePool.get(i).setDownloaded();
        }
        
    }

    private int getNextId() {
        return idCounter++;
    }
	
	public Network generateConnections(List<Node> nodes) {
		Network network = new Network();
		for (Node node : nodes) {
			if (node.getLinksSize() == 1) {
				network.addPeer((Peer) node.getTrueForm());
			} else {
				network.addISP((ISP) node.getTrueForm());
			}
			for (Node link : node.getLinks()) {
				Connection connection;
				if (node.getLinksSize() == 1) {
					connection = new Connection(node.getTrueForm(), link.getTrueForm(),
							getRandom(stage1Stability),
							getRandom(stage1Bandwidth));
				} else if (node.getLinksSize() < 5 ){
					connection = new Connection(node.getTrueForm(), link.getTrueForm(),
							getRandom(stage2Stability),
							getRandom(stage2Bandwidth));
				} else {
					connection = new Connection(node.getTrueForm(), link.getTrueForm(),
							getRandom(stage3Stability),
							getRandom(stage3Bandwidth));
				}
				node.getTrueForm().connect(connection);
			}
		}
		return network;
	}
	
	public int getRandom(int seed) {
		return Math.max(random.nextInt((int)(seed * 0.2) + (int)(seed * 0.8)), 1);
	}
	
	public double getRandom(double seed) {
		return random.nextDouble() * 0.2 + (seed * 0.8);
	}
}
